namespace Jsl.FxCop
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Reflection;
    using System.Resources;
    using System.Runtime.Serialization;
    using System.Security.Permissions;
    using Microsoft.FxCop.Sdk;

    /// <summary>
    /// Holds the collection of <see cref="ComponentInfo"/> for a specific component.
    /// </summary>
    [Serializable]
    public class ComponentDictionary : Dictionary<Node, ComponentInfo>, IDisposable
    {
        private readonly IDictionary<PropertyNode, Field> componentAccessors;
        private readonly TypeNode type;
        private ResourceSet resources;

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="ComponentDictionary"/> class.
        /// </summary>
        /// <param name="type">The type to process.</param>
        public ComponentDictionary(TypeNode type)
        {
            this.type = type;

            this.componentAccessors = ComponentProcessor.GetComponentAccessors(type);
            ComponentProcessor.ProcessInitializeComponent(this);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ComponentDictionary"/> class.
        /// </summary>
        /// <param name="info">A <see cref="T:System.Runtime.Serialization.SerializationInfo"></see> object containing the information required to serialize the <see cref="T:System.Collections.Generic.Dictionary`2"></see>.</param>
        /// <param name="context">A <see cref="T:System.Runtime.Serialization.StreamingContext"></see> structure containing the source and destination of the serialized stream associated with the <see cref="T:System.Collections.Generic.Dictionary`2"></see>.</param>
        protected ComponentDictionary(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }

        /// <summary>
        /// Finalizes an instance of the <see cref="ComponentDictionary"/> class.
        /// </summary>
        ~ComponentDictionary()
        {
            this.Dispose(false);
        }
        #endregion Constructors

        #region Public Properties
        /// <summary>
        /// Gets the component accessors.
        /// </summary>
        /// <value>The component accessors.</value>
        /// <remarks>
        /// Sometimes component fields are accessed through a property. This happens in VB but may also happen in C#.
        /// You can use this Dictionary to get the field, if any, that a property is the accessor for.
        /// </remarks>
        public IDictionary<PropertyNode, Field> ComponentAccessors
        {
            get
            {
                return this.componentAccessors;
            }
        }

        /// <summary>
        /// Gets the resources for the component.
        /// </summary>
        /// <value>The resources for the component.</value>
        public ResourceSet Resources
        {
            get
            {
                if (this.resources == null)
                {
                    var resourceName = this.Type.FullName + ".resources";
                    foreach (Resource resource in this.Type.DeclaringModule.ContainingAssembly.Resources)
                    {
                        if (resource.Name == resourceName)
                        {
                            AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(this.CurrentDomain_AssemblyResolve);
                            try
                            {
                                using (System.IO.Stream stream = new System.IO.MemoryStream(resource.Data))
                                {
                                    this.resources = new ResourceSet(stream);
                                }
                            }
                            finally
                            {
                                AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(this.CurrentDomain_AssemblyResolve);
                            }

                            break;
                        }
                    }
                }

                return this.resources;
            }
        }

        /// <summary>
        /// Gets the type that contains the InitializeComponent.
        /// </summary>
        /// <value>The processed type.</value>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "The name is too good to change.")]
        public TypeNode Type
        {
            get
            {
                return this.type;
            }
        }
        #endregion Public Properties

        #region Public Methods
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Enumerates the ancestry of the component. This includes the component.
        /// </summary>
        /// <param name="component">The component.</param>
        /// <returns>The component's ancestry.</returns>
        public IEnumerable<ComponentInfo> EnumerateAncestry(ComponentInfo component)
        {
            var thisComponent = component;
            while (thisComponent != null)
            {
                yield return thisComponent;

                Node parentNode;
                if (!thisComponent.TryGetValue<Node>("Parent", out parentNode))
                {
                    yield break;
                }

                this.TryGetValue(parentNode, out thisComponent);
            }
        }

        /// <summary>
        /// Enumerates the siblings for this component.
        /// </summary>
        /// <param name="component">The component.</param>
        /// <returns>The siblings of the specified component.</returns>
        /// <remarks>This is intended to be use in a <c>foreach</c> statement.</remarks>
        public IEnumerable<ComponentInfo> EnumerateSiblings(ComponentInfo component)
        {
            foreach (KeyValuePair<Node, ComponentInfo> entry in this)
            {
                if (!object.ReferenceEquals(entry.Value, component))
                {
                    Node componentParent;
                    Node entryParent;
                    if (component.TryGetValue<Node>("Parent", out componentParent)
                        && entry.Value.TryGetValue<Node>("Parent", out entryParent)
                        && object.ReferenceEquals(componentParent, entryParent))
                    {
                        yield return entry.Value;
                    }
                }
            }
        }

        /// <summary>
        /// Implements the <see cref="T:System.Runtime.Serialization.ISerializable"></see> interface and returns the data needed to serialize the <see cref="T:System.Collections.Generic.Dictionary`2"></see> instance.
        /// </summary>
        /// <param name="info">A <see cref="T:System.Runtime.Serialization.SerializationInfo"></see> object that contains the information required to serialize the <see cref="T:System.Collections.Generic.Dictionary`2"></see> instance.</param>
        /// <param name="context">A <see cref="T:System.Runtime.Serialization.StreamingContext"></see> structure that contains the source and destination of the serialized stream associated with the <see cref="T:System.Collections.Generic.Dictionary`2"></see> instance.</param>
        /// <exception cref="T:System.ArgumentNullException">info is null.</exception>
        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            base.GetObjectData(info, context);
        }
        #endregion Public Methods

        #region Protected Methods
        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (this.resources != null)
            {
                this.resources.Dispose();
                this.resources = null;
            }
        }
        #endregion Protected Methods

        #region Private Methods
        /// <summary>
        /// Handles the AssemblyResolve event of the CurrentDomain control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="args">The <see cref="System.ResolveEventArgs"/> instance containing the event data.</param>
        /// <returns>The assembly that failed to load.</returns>
        /// <remarks>
        /// Some resources are require specific types to be loaded. Since FxCop runs in a separate directory, the assemblies
        /// can't always be found. In this method we try to find the appropriate assembly in the same directory as the
        /// assembly being analyzed.
        /// </remarks>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom", Justification = "By design.")]
        private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            var directory = Path.GetDirectoryName(this.Type.DeclaringModule.Location);

            var assemblyName = new AssemblyName(args.Name);
            var location = Path.Combine(directory, assemblyName.Name + ".dll");

            return Assembly.LoadFrom(location);
        }
        #endregion Private Methods
    }
}